package jp.co.nes.awssupport.common.base;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.services.stepfunctions.model.GetActivityTaskResult;

import jp.co.nes.awssupport.common.bean.BaseBean;
import jp.co.nes.awssupport.common.utility.AppInfoUtil;
import jp.co.nes.awssupport.common.utility.AwsStepFunctionsHelper;
import jp.co.nes.awssupport.common.utility.JsonUtil;

/**
 * AWS StepFunctionsのアクティビティを実装する際に使用する抽象クラスです。
 *
 * @param <I> 入力情報
 * @param <O> 出力情報
 */
public abstract class AbstractActivityTaskClient<I extends BaseBean, O extends BaseBean> extends AbstractClient {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(AbstractActivityTaskClient.class);

    /** AwsStepFunctionsヘルパー */
    protected AwsStepFunctionsHelper stepFunctionsHelper;

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.common.base.AbstractClient#execute()
     */
    @Override
    protected void execute() {
        logger.info("execute start");

        String category = "ActivityTaskClient";
        int timeout = AppInfoUtil.getIntValAndMinChk(category, "StepFunctionsClientConnectionTimeout", 65000, 0);
        long pollingMaxTime = AppInfoUtil.getLongValAndMinChk(category, "PollingMaxTime", 300000, 0);
        long sleepTime = AppInfoUtil.getLongValAndMinChk(category, "SleepTime", 1000, 0);
        if (logger.isDebugEnabled()) {
            logger.debug(category + ",StepFunctionsClientConnectionTimeout=" + timeout);
            logger.debug(category + ",PollingMaxTime=" + pollingMaxTime);
            logger.debug(category + ",SleepTime=" + sleepTime);
        }

        stepFunctionsHelper = createAwsStepFunctionsHelper(timeout);
        long pollingStart = System.currentTimeMillis();
        while (true) {
            GetActivityTaskResult activityTaskResult = stepFunctionsHelper.getActivityTask(getActivityArn());
            if (activityTaskResult == null) {
                checkLoopTime(pollingStart, pollingMaxTime);
                sleep(sleepTime);
                continue;
            }

            try {
                logger.info("runActivityTask start");
                O output = runActivityTask(getInput(activityTaskResult));
                logger.info("runActivityTask end");
                if (output == null) {
                    logger.info("output is null");
                } else {
                    logger.info("output=" + output);
                }

                sendTaskSuccess(activityTaskResult.getTaskToken(), output);
            } catch (Throwable e) {
                sendTaskFailure(activityTaskResult.getTaskToken(), e);
                throw new CommonException(e);
            }
            break;
        }

        logger.info("execute end");
    }

    /**
     * AwsStepFunctionsヘルパーを生成します。
     *
     * @param timeout AwsStepFunctionsヘルパーに設定するコネクションタイムアウト
     * @return AwsStepFunctionsヘルパー
     */
    protected AwsStepFunctionsHelper createAwsStepFunctionsHelper(int timeout) {
        return new AwsStepFunctionsHelper() {
            /*
             * (非 Javadoc)
             * @see jp.co.nes.awssupport.common.utility.AwsHelperBase#createClientConfiguration(java.lang.String, java.lang.String, java.lang.String)
             */
            @Override
            protected ClientConfiguration createClientConfiguration(String proxyHost, String proxyPort,
                    String nonProxyHosts) {
                ClientConfiguration config = super.createClientConfiguration(proxyHost, proxyPort, nonProxyHosts);
                if (config == null) {
                    config = new ClientConfiguration();
                }
                logger.debug("StepFunctionsClientConnectionTimeout setting");
                config.setConnectionTimeout(timeout);
                return config;
            }
        };
    }

    /**
     * ポーリングを続行するか確認します。
     *
     * @param pollingStart ポーリング開始時間
     * @param maxTime ポーリングの最大時間
     */
    protected void checkLoopTime(long pollingStart, long maxTime) {
        long pollingTime = System.currentTimeMillis() - pollingStart;
        if (pollingTime > maxTime) {
            throw new CommonException("activity task can not get");
        } else {
            logger.debug("activity task can not get and retry");
        }
    }

    /**
     * スリープを実行します。
     *
     * @param sleepTime スリープする時間
     */
    protected void sleep(long sleepTime) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            // ワーニングログを出力し、処理を続行する。
            logger.warn(e.getMessage(), e);
        }
    }

    /**
     * アクティビティのarnを返却します。
     *
     * @return アクティビティのarn
     */
    protected abstract String getActivityArn();

    /**
     * 入力情報を返却します。
     *
     * @param activityTaskResult GetActivityTaskResult
     * @return 入力情報
     */
    protected I getInput(GetActivityTaskResult activityTaskResult) {
        String inputValue = activityTaskResult.getInput();
        if (StringUtils.isEmpty(inputValue)) {
            logger.info("inputValue is null or empty");
        } else {
            logger.info("inputValue=" + inputValue);
        }

        I input = JsonUtil.convertJsonStringToBean(inputValue, getInputClass());
        if (input == null) {
            logger.info("input is null");
        } else {
            logger.info("input=" + input);
        }

        return input;
    }

    /**
     * アクティビティの処理を実行します。
     *
     * @param input 入力情報
     * @return 出力情報
     */
    protected abstract O runActivityTask(I input);

    /**
     * 入力情報のClassオブジェクトを返却します。
     *
     * @return 入力情報のClassオブジェクト
     */
    protected abstract Class<I> getInputClass();

    /**
     * アクティビティに成功を送信します。
     *
     * @param taskToken タスクトークン
     * @param output 出力情報
     */
    protected void sendTaskSuccess(String taskToken, O output) {
        // 成功を送信
        stepFunctionsHelper.sendTaskSuccess(taskToken, output);
    }

    /**
     * アクティビティに失敗を送信します。
     *
     * @param taskToken タスクトークン
     * @param throwable 発生した例外
     */
    protected void sendTaskFailure(String taskToken, Throwable throwable) {
        // 送信内容の作成
        String error = "Failure";
        String cause = "Failure";
        if (throwable != null) {
            error = throwable.getMessage();
            cause = null;
            Throwable causeThrowable = throwable.getCause();
            if (causeThrowable != null) {
                cause = causeThrowable.getMessage();
            }
        }

        if (!StringUtils.isEmpty(error) && error.length() > 256) {
            // 2017-12-19時点のStep FunctionsのAPI仕様によると、
            // sendTaskFailureのerrorに設定できるのは最大256文字なので、
            // 最大長を超えていた場合は、256文字より後ろを削除する。
            error = error.substring(0, 256);
        }
        if (!StringUtils.isEmpty(cause) && cause.length() > 32768) {
            // 2017-12-19時点のStep FunctionsのAPI仕様によると、
            // sendTaskFailureのcauseに設定できるのは最大32768文字なので、
            // 最大長を超えていた場合は、32768文字より後ろを削除する。
            cause = cause.substring(0, 32768);
        }

        // 失敗を送信
        stepFunctionsHelper.sendTaskFailure(taskToken, error, cause);
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.common.base.AbstractClient#finish(boolean, java.lang.Throwable)
     */
    @Override
    protected void finish(boolean isSuccess, Throwable throwable) {
        // 特に何もしない。
    }
}
